#include <string.h>

#include "lib.h"
#include "stream.h"
#include "pathname.h"
#include "dir.h"

#include "packagemanagement.h"
#include "pkgmfest.h"
#include "pkgmgr.h"
#include "pkg.h"
#include "mfestprv.h"
#include "apppath.h"
using namespace PackageManagement;

//
// ----------------------- InstalledPackagesManifest
//
// List of installed packages.  NumFiles in PMHead is not used (== 0).
//

InstalledPackagesManifest::InstalledPackagesManifest(const String& filename)
{
	m_filename = filename;	// NOTE: filename MUST exist over life of "this"
}

bool8
InstalledPackagesManifest::load(pPackageMgr packageManager)
{
	pCStream	file=0;
	pPackage	package=0;
	uint32		i;
	PMHead		pmh;
	PMPackage	pmp;
	PackageManifest pm;

	if (m_filename.isEmpty())
		goto abort;

	file = newCStream(m_filename, streamO_RDONLY);
	if (!file)
		goto abort;

	if (!pmh.load(file))
		goto abort;

	for (i = 0; i < pmh.NumPkgs; i++)
	{
		package = new Package();
		if (!package)
			goto abort;

		if (pmp.load(file, pmh, package))
		{
			packageManager->addPackage(package);

			pm.loadFiles(package, packageManager);

			package = (Package*)0;
		}
		else
			goto abort;
	}

	deleteCStream(file);
	return true;

abort:
	delete package;
	deleteCStream(file);
	return false;
}

bool8
InstalledPackagesManifest::save(pPackageMgr packageManager)
{
	HashTableIterator<pPackage> *	iterator;
	pCStream	file=0;
	int32		i;
	PMHead		pmh;
	PMPackage	pmp;

	if (!m_filename)
		goto abort;

	file = newCStream(m_filename, 0);
	if (!file)
		goto abort;

	i = packageManager->packageCount();

	if (!pmh.save(file, i, 0))
		goto abort;

	iterator = packageManager->iterator();
	if (iterator)
	{
		for (iterator->begin(); iterator->key(); iterator->operator++(0))
		{
			if (!pmp.save(file, iterator->value()))
			{
				delete iterator;
				goto abort;
			}
		}

		delete iterator;
	}

	deleteCStream(file);
	return true;

abort:
	deleteCStream(file);
	return false;
}

//
// --------------------------- PackageManifest
// 
// PMHead with only 1 PMPackage.  PMHead.NumFiles is used (along with the
// filenames following the PMPackage) and contains the list of filenames
// associated with the PMPackage (specifically, these files are not shared with
// any other PMPackage).
//
// All pathnames stored within the manifest use Unix separators ('/').
//

bool8
PackageManifest::load(pPackage package)
{
	PathName filename;
	pCStream file = (pCStream)0;
	PMHead pmh;
	PMPackage pmp;
	Package p;

	if (!filename.set(AppPath::getManifestDir()) ||
		!filename.concat(package->getName()))
		goto abort;

	file = newCStream(filename, streamO_RDONLY);
	if (!file)
		goto abort;

	if (!pmh.load(file))
		goto abort;

	if (pmh.NumPkgs != 1)
		goto abort;

	if (!pmp.load(file, pmh, &p))
		goto abort;

	package->copy(p);

	deleteCStream(file);
	return true;

abort:
	deleteCStream(file);
	return false;
}

bool8
PackageManifest::loadFiles(pPackage package, pPackageMgr packageManager)
{
	PathName filename, tokill;
	pCStream file = (pCStream)0;
	PMHead pmh;
	uint8 buf[259], ch;
	int32 used, amt, nRead;

	if (!filename.set(AppPath::getManifestDir()) ||
		!filename.concat(package->getName()))
		goto abort;

	file = newCStream(filename, streamO_RDONLY);
	if (!file)
		goto abort;

	if (!pmh.load(file))
		goto abort;
	
	if (pmh.NumPkgs != 1 || pmh.FilenameOfs == 0)
		goto abort;

	if (streamEOF == CStreamSeek(file, pmh.FilenameOfs, streamSEEK_SET))
		goto abort;

	// read filenames & uninstall files...skip missing files
	//
	amt = used = 0;
	while (pmh.NumFilenames > 0)
	{
		if (used >= amt)
		{
			if (amt >= 256)
				amt = 0;

			nRead = CStreamRead(file, buf + amt, 256 - amt);
			if (nRead == streamEOF)
				goto abort;
			amt += nRead;
			used = 0;
		}

		if (used + buf[used] < amt)
		{
			ch = buf[used + buf[used] + 1];
			buf[used + buf[used] + 1] = '\0';

			tokill.set((char*)buf + used + 1);

			buf[used + buf[used] + 1] = ch;
			used += buf[used] + 1;
		}
		else
		{
            ASSERT( amt >= used );
			memmove(buf, buf + used, amt - used);
			amt -= used;
			used = amt;
			continue;
		}

		// change unix separators stored in manifest
		//
		tokill.replace('/', Directory::sep);

		packageManager->addFile(tokill);

		--pmh.NumFilenames;
	}

	deleteCStream(file);

	return true;

abort:
	deleteCStream(file);
	return false;
}


bool8
PackageManifest::uninstall(pPackage package, pPackageMgr packageManager)
{
	PathName filename, tokill;
	pCStream file = (pCStream)0;
	PMHead pmh;
	uint8 buf[259], ch;
	int32 used, amt, nRead;

	if (!filename.set(AppPath::getManifestDir()) ||
		!filename.concat(package->getName()))
		goto abort;

	file = newCStream(filename, streamO_RDONLY);
	if (!file)
		goto abort;

	if (!pmh.load(file))
		goto abort;
	
	if (pmh.NumPkgs != 1 || pmh.FilenameOfs == 0)
		goto abort;

	if (streamEOF == CStreamSeek(file, pmh.FilenameOfs, streamSEEK_SET))
		goto abort;

	// read filenames & uninstall files...skip missing files
	//
	amt = used = 0;
	while (pmh.NumFilenames > 0)
	{
		if (used >= amt)
		{
			if (amt >= 256)
				amt = 0;

			nRead = CStreamRead(file, buf + amt, 256 - amt);
			if (nRead == streamEOF)
				goto abort;
			amt += nRead;
			used = 0;
		}

		if (used + buf[used] < amt)
		{
			ch = buf[used + buf[used] + 1];
			buf[used + buf[used] + 1] = '\0';

			tokill.set((char*)buf + used + 1);

			buf[used + buf[used] + 1] = ch;
			used += buf[used] + 1;
		}
		else
		{
            ASSERT( amt >= used );
			memmove(buf, buf + used, amt - used);
			amt -= used;
			used = amt;
			continue;
		}

		// change unix separators stored in manifest
		//
		tokill.replace('/', Directory::sep);

		packageManager->removeFile(tokill);

		--pmh.NumFilenames;
	}

	deleteCStream(file);

	// delete manifest file itself
	//
	CStreamDelFile(filename);

	return true;

abort:
	deleteCStream(file);
	return false;
}


